<?php

use Yaf\Response_Abstract;
use Yaf\Response\Http;
use Yaf\Response\Cli;

/**
 * Response 响应类
 * - 扩展Yaf的响应类功能：支持CLI运行模式
 * - 拥有各种结果返回状态 ，以及对返回结果 的格式化
 * - 其中：200成功，400非法请求，500服务器错误
 *
 * Class Response
 */
class Response
{
    /**
     * 返回状态码，其中：200成功，400非法请求，500服务器错误
     * @var int $ret
     */
    protected $ret = 200;

    /**
     * 待返回给客户端的数据
     * @var array
     */
    protected $data = array();

    /**
     * 错误返回信息
     * @var string $msg
     */
    protected $msg = '';

    /**
     * 调试信息
     * @var array $debug
     */
    protected $debug = array();

    /**
     * Yaf响应对象
     * @var Response_Abstract|Http|Cli
     */
    protected $response = null;

    /**
     * Response constructor.
     * @param Response_Abstract $response
     */
    final public function __construct(Response_Abstract $response)
    {
        $this->response = $response;
    }

    /** ------------------ setter ------------------ **/

    /**
     * 设置返回状态码
     * @param int $ret 返回状态码，其中：200成功，400非法请求，500服务器错误
     * @return Response
     */
    public function setRet(int $ret): Response
    {
        $this->ret = $ret;
        return $this;
    }

    /**
     * 设置返回数据
     * @param array|string|int|bool|float|object $data 待返回给客户端的数据，建议使用数组，方便扩展升级
     * @return Response
     */
    public function setData($data): Response
    {
        $this->data = $data;
        return $this;
    }

    /**
     * 设置错误信息
     * @param string $msg 错误信息
     * @return Response
     */
    public function setMsg(string $msg): Response
    {
        $this->msg = $msg;
        return $this;
    }

    /**
     * 同时设置数据、状态码、错误信息
     * @param array|string|int|bool|float|object $data
     * @param int $ret
     * @param string $msg
     * @return $this
     */
    public function setAll($data, int $ret, string $msg): Response
    {
        $this->data = $data;
        $this->ret = $ret;
        $this->msg = $msg;
        return $this;
    }

    /**
     * 设置调试信息
     * @param string $key        键值标识
     * @param   mixed   $value      调试数据
     * @return  Response
     */
    public function setDebug(string $key, $value): Response
    {
        if (true) {
            $this->debug[$key] = $value;
        }
        return $this;
    }

    /**
     * 添加报文头部
     * @param string $key 名称
     * @param string $content 内容
     */
    public function addHeaders(string $key, string $content)
    {
        static::handleHeaders($this->response, [$key => $content]);
    }

    /**
     * 结果输出
     */
    public function output()
    {
        // Step 1. 获取数据结果
        $rs = $this->getResult();

        // Step 2. 序列化结果
        $rs = $this->formatResult($rs);

        // Step 3. 输出结果
        $this->echoResult($rs);
    }

    /**
     * echo结果
     * @param $rs
     */
    protected function echoResult($rs)
    {
        // 设置响应正文内容
        if (false) {
            $this->response->setBody($rs);
        } else {
            echo $rs;
        }
    }


    /** ------------------ getter ------------------ **/


    /**
     * 根据状态码调整Http响应状态码
     */
    public function adjustHttpStatus(): Response
    {
        $httpStatus = array(
            100 => 'Continue',
            101 => 'Switching Protocols',
            200 => 'OK',
            201 => 'Created',
            202 => 'Accepted',
            203 => 'Non-Authoritative Information',
            204 => 'No Content',
            205 => 'Reset Content',
            206 => 'Partial Content',
            300 => 'Multiple Choices',
            301 => 'Moved Permanently',
            302 => 'Found',
            303 => 'See Other',
            304 => 'Not Modified',
            305 => 'Use Proxy',
            307 => 'Temporary Redirect',
            400 => 'Bad Request',
            401 => 'Unauthorized',
            402 => 'Payment Required',
            403 => 'Forbidden',
            404 => 'Not Found',
            405 => 'Method Not Allowed',
            406 => 'Not Acceptable',
            407 => 'Proxy Authentication Required',
            408 => 'Request Time-out',
            409 => 'Conflict',
            410 => 'Gone',
            411 => 'Length Required',
            412 => 'Precondition Failed',
            413 => 'Request Entity Too Large',
            414 => 'Request-URI Too Large',
            415 => 'Unsupported Media Type',
            416 => 'Requested range not satisfiable',
            417 => 'Expectation Failed',
            500 => 'Internal Server Error',
            501 => 'Not Implemented',
            502 => 'Bad Gateway',
            503 => 'Service Unavailable',
            504 => 'Gateway Time-out',
            505 => 'HTTP Version not supported',
        );

        $protocol = $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1';
        $str = "{$protocol} {$this->ret} " . ($httpStatus[$this->ret] ?? 'Unknown Http Status Code');
        @header($str);

        return $this;
    }

    /**
     * 获取返回结果
     */
    protected function getResult(): array
    {
        $rs = array(
            'ret'   => $this->ret,
            'data'  => is_array($this->data) && empty($this->data) ? (object)$this->data : $this->data,
            'msg'   => $this->msg,
        );

        if (!empty($this->debug)) {
            $rs['debug'] = $this->debug;
        }

        return $rs;
    }

    /**
     * 格式化需要输出返回的结果
     * - Json序列化
     * @param $result
     * @return false|string
     */
    protected function formatResult($result)
    {
        $this->addHeaders('Content-Type', 'application/json; charset=utf-8');
        return json_encode($result, JSON_UNESCAPED_UNICODE);
    }

    /**
     * 设置响应报文头
     * @param Response_Abstract $response
     * @param array $header
     */
    public static function handleHeaders(Response_Abstract $response, array $header = [])
    {
        if (method_exists($response, 'setHeader') && is_callable([$response, 'setHeader'])) {
            // FPM模式
            array_walk($header, function ($content, $key) use ($response) {
                $response->setHeader($key, $content);
            });
        } else {
            // CLI模式
            array_walk($header, function ($content, $key) {
                @header($key . ': ' . $content);
            });
        }
    }
}
